/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ivan.cheng.tuqu.crop;

import ivan.cheng.tuqu.Globals;
import ivan.cheng.tuqu.R;
import ivan.cheng.tuqu.activity.EffectsProcessingActivity;
import ivan.cheng.tuqu.util.BitmapUtil;
import ivan.cheng.tuqu.util.FileUtil;
import ivan.cheng.tuqu.util.LogUtil;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.media.FaceDetector;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;

/**
 * The activity can crop specific region of interest from an image.
 */
public class CropImage extends MonitoredActivity implements OnClickListener {
	// private static final String TAG = "CropImage";

	// These are various options can be specified in the intent.
	private Bitmap.CompressFormat mOutputFormat = Bitmap.CompressFormat.JPEG; // only
																				// used
																				// with
																				// mSaveUri
	private Uri mSaveUri = null;
	private int mAspectX, mAspectY;
	private boolean mDoFaceDetection = true;
	private boolean mCircleCrop = false;
	private final Handler mHandler = new Handler();

	// These options specifiy the output image size and whether we should
	// scale the output to fit it (or just crop it).
	private int mOutputX, mOutputY;
	private boolean mScale = true;
	private boolean mScaleUp = true;
	private boolean mSqure = true;//
	boolean mWaitingToPick; // Whether we are wait the user to pick a face.
	boolean mSaving = false; // Whether the "save" button is already clicked.

	private CropImageView mImageView;
	private ContentResolver mContentResolver;

	private Bitmap mBitmap;
	//private LinearLayout mLL_land;
	private LinearLayout mLL_port;
	private final BitmapManager.ThreadSet mDecodingThreads = new BitmapManager.ThreadSet();
	HighlightView mCrop;

	// private IImageList mAllImages;
	// private IImage mImage;
	private boolean mbFromCamera = false;
	private String mOriginImagePath;
	private boolean mbStartedBySystem = false;

	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		mContentResolver = getContentResolver();

		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.cropimage);

		mImageView = (CropImageView) findViewById(R.id.image);

		// MenuHelper.showStorageToast(this);

		Intent intent = getIntent();
		Bundle extras = intent.getExtras();

		if (extras != null) {
			if (extras.getString("circleCrop") != null) {
				mCircleCrop = true;
				mAspectX = 1;
				mAspectY = 1;
			}
			mSaveUri = (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT);
			if (mSaveUri != null) {
				String outputFormatString = extras.getString("outputFormat");
				if (outputFormatString != null) {
					mOutputFormat = Bitmap.CompressFormat
							.valueOf(outputFormatString);
				}
			}
			mBitmap = (Bitmap) extras.getParcelable("data");
			mAspectX = extras.getInt("aspectX");
			mAspectY = extras.getInt("aspectY");
			mOutputX = extras.getInt("outputX");
			mOutputY = extras.getInt("outputY");
			mScale = extras.getBoolean("scale", true);
			mSqure= extras.getBoolean("squre", true);
			mScaleUp = extras.getBoolean("scaleUpIfNeeded", true);
			mDoFaceDetection = extras.containsKey("noFaceDetection") ? !extras
					.getBoolean("noFaceDetection") : true;
			mbFromCamera = extras.getBoolean(Globals.EXTRA_IMAGE_SOURCE);
		}

		if ("android.intent.action.SEND".equals(getIntent().getAction())
				|| "android.intent.action.CROP".equals(getIntent().getAction())) {// 证明是从系统发过来的图
			mbStartedBySystem = true;
			Bundle bd = getIntent().getExtras();
			if (null != bd) {
				Uri url = (Uri) bd.get(Intent.EXTRA_STREAM);
				if (url.toString().startsWith("content://")) {
					Cursor cursor = getContentResolver().query(url, null, null,
							null, null);
					if (cursor == null) {
						return;
					}
					cursor.moveToFirst();
					mOriginImagePath = cursor.getString(1); // 图片文件路径

				} else if (url.toString().startsWith("file://")) {
					mOriginImagePath = url.toString().replace("file://", "");
				} else if (url.toString().startsWith("data://")) {
					mOriginImagePath = url.toString().replace("data://", "");
				} else {
					LogUtil.i("File Error: " + url.toString());
					finish();
				}
			}
		} else {
			mOriginImagePath = getIntent().getExtras().getString(
					Globals.EXTRA_PHOTO_PATH);
		}
		int sWidth = Util.GetBigImageDimesion(this)* 2;
		;
		int sHeight = sWidth*3/2;
		// 给他降一点大小防止有些图片我们适应不了
		mBitmap = BitmapUtil.instance().ratioBitmap(mOriginImagePath, sWidth,
				sHeight);
		if (mBitmap == null) {
			finish();
			return;
		}
		// mAspectX = 1;//extras.getInt("aspectX");
		// mAspectY = 1;//extras.getInt("aspectY");
		// 根据拍摄的方向旋转图像（纵向拍摄时需要将图像旋转90度）
		// if (getWindowManager().getDefaultDisplay().getOrientation() == 0){
		// Matrix matrix = new Matrix();
		// matrix.postRotate(90);
		// Bitmap tembmp = mBitmap;
		// mBitmap = BitmapUtil.instance().createBitmap(mBitmap, 0, 0,
		// mBitmap.getWidth(), mBitmap.getHeight(),matrix, true);
		// if(tembmp != mBitmap)
		// tembmp.recycle();
		// }
		// if (mBitmap == null) {
		// Uri target = intent.getData();
		// // Bitma
		// mAllImages = ImageManager.makeImageList(target,
		// // mContentResolver, ImageManager.SORT_ASCENDING);
		// mImage = mAllImages.getImageForUri(target);
		// if (mImage != null) {
		// // Don't read in really large bitmaps. Use the (big) thumbnail
		// // instead.
		// // TODO when saving the resulting bitmap use the
		// // decode/crop/encode api so we don't lose any resolution.
		// mBitmap = mImage.thumbBitmap(IImage.ROTATE_AS_NEEDED);
		// }
		// }

		initView();
		Toast.makeText(this, getString(R.string.tips_crop), Toast.LENGTH_LONG).show();
	}

	private void initView() {
		// Make UI fullscreen.
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

		findViewById(R.id.btn_back).setOnClickListener(this);
		findViewById(R.id.btn_ok).setOnClickListener(this);
		findViewById(R.id.discard).setOnClickListener(this);
		findViewById(R.id.btn_rotate_left).setOnClickListener(this);
		findViewById(R.id.btn_rotate_right).setOnClickListener(this);
		findViewById(R.id.save).setOnClickListener(this);
//		findViewById(R.id.discard_land).setOnClickListener(this);
//		findViewById(R.id.btn_rotate_left_land).setOnClickListener(this);
//		findViewById(R.id.btn_rotate_right_land).setOnClickListener(this);
//		findViewById(R.id.save_land).setOnClickListener(this);
//		mLL_land = (LinearLayout) findViewById(R.id.LL_land);
		mLL_port = (LinearLayout) findViewById(R.id.LL_port);
		if (getWindowManager().getDefaultDisplay().getOrientation() == 0) {
			//mLL_land.setVisibility(View.INVISIBLE);
			mLL_port.setVisibility(View.VISIBLE);
		} else {
			//mLL_land.setVisibility(View.VISIBLE);
			mLL_port.setVisibility(View.INVISIBLE);
		}
		startFaceDetection();
	}

	// public void showStorageToast(Activity activity, int remaining) {
	// String noStorageText = null;
	//
	// if (remaining == MenuHelper.NO_STORAGE_ERROR) {
	// String state = Environment.getExternalStorageState();
	// if (state == Environment.MEDIA_CHECKING) {
	// noStorageText = activity.getString(R.string.preparing_sd);
	// } else {
	// noStorageText = activity.getString(R.string.no_storage);
	// }
	// } else if (remaining < 1) {
	// noStorageText = activity.getString(R.string.not_enough_space);
	// }
	//
	// if (noStorageText != null) {
	// Toast.makeText(activity, noStorageText, 5000).show();
	// }
	// }

	private void startFaceDetection() {
		if (isFinishing()) {
			return;
		}

		mImageView.setImageBitmapResetBase(mBitmap, true);

		Util.startBackgroundJob((MonitoredActivity) this, "", getResources()
				.getString(R.string.runningFaceDetection), new Runnable() {
			public void run() {
				final CountDownLatch latch = new CountDownLatch(1);
				final Bitmap b = /*
								 * (mImage != null) ?
								 * mImage.fullSizeBitmap(IImage.UNCONSTRAINED,
								 * 1024 * 1024) :
								 */mBitmap;
				mHandler.post(new Runnable() {
					public void run() {
						if (b != mBitmap && b != null) {
							mImageView.setImageBitmapResetBase(b, true);
							mBitmap.recycle();
							mBitmap = b;
						}
						if (mImageView.getScale() == 1F) {
							mImageView.center(true, true);
						}
						latch.countDown();
					}
				});
				try {
					latch.await();
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				mRunFaceDetection.run();
			}
		}, mHandler);
	}

	private void onRotateClicked(int iAngle) {
		Matrix matrix = new Matrix();
		// miRotateAngle = (miRotateAngle + iAngle)%360;

		matrix.postRotate(iAngle);
		Bitmap bm = BitmapUtil.instance().createBitmap(mBitmap, 0, 0,
				mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);
		mImageView.setImageBitmap(bm);
		mBitmap.recycle();
		mBitmap = bm;
		mCrop.RotateImageRect(iAngle,mAspectX,mAspectY);
	}

	private void onSaveClicked() 
	{
		// TODO this code needs to change to use the decode/crop/encode single
		// step api so that we don't require that the whole (possibly large)
		// bitmap doesn't have to be read into memory
		if (mCrop == null) {
			return;
		}

		if (mSaving)
			return;

		mSaving = true;

		Rect r = mCrop.getCropRect();

		// 对不同的屏幕分辨率做同一处理
		DisplayMetrics dm = new DisplayMetrics();
		WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
		wm.getDefaultDisplay().getMetrics(dm);
		
		float fRodio = 1.0f;
		if(r.width() > Util.GetBigImageDimesion(this))
		{
			fRodio = (float)Util.GetBigImageDimesion(this)/r.width();
		}
		int width = (int) (r.width() * fRodio);
		int height = (int) (r.height() * fRodio);
		Bitmap croppedImage = null;
//		if(1.0f == fRodio)
//		{
//			LogUtil.d("no crop at all~~");
//			croppedImage = mBitmap;
//		}
//		else
		{
			croppedImage = BitmapUtil.instance().createBitmap(width, height,
					/*mCircleCrop ? */Bitmap.Config.ARGB_8888/* : Bitmap.Config.RGB_565*/);
			{
				if (null == croppedImage) 
				{
					Toast.makeText(this, getString(R.string.save_failured),
							Toast.LENGTH_LONG).show();
					return;
				}
				Canvas canvas = new Canvas(croppedImage);
				Rect dstRect = new Rect(0, 0, width, height);
				canvas.drawBitmap(mBitmap, r, dstRect, null);
			}

		}

		
		
		Bundle extras = new Bundle();
		// int iWidth =Setting.GetBigImageDimesion(this);
		String path = Globals.MX_FILE_PATH_HOME + "croptem.jpg";
		boolean result = FileUtil.getInstance().storeImageToFile(croppedImage,
				path,Bitmap.CompressFormat.PNG,100);
		croppedImage.recycle();
		extras.putString(Globals.EXTRA_PHOTO_PATH, path);
		extras.putBoolean(Globals.EXTRA_IMAGE_SOURCE, mbFromCamera);
		if (false == mbStartedBySystem) 
		{
			// Return the cropped image directly or save it to the specified
			// URI.
			Bundle myExtras = getIntent().getExtras();
			if (myExtras != null
					&& (myExtras.getParcelable("data") != null || myExtras
							.getBoolean("return-data"))) {

				if (true == result)
					setResult(RESULT_OK, (new Intent())
							.setAction("inline-data").putExtras(extras));
				else {
					Toast.makeText(this,
							getString(R.string.save_image_failure_toast_msg),
							Toast.LENGTH_LONG);
					setResult(RESULT_CANCELED);
				}

				finish();
			} 
			else 
			{
				final Bitmap b = croppedImage;
				Util.startBackgroundJob(this, null, getResources().getString(
						R.string.savingImage), new Runnable() {
					public void run() {
						saveOutput(b);
					}
				}, mHandler);
			}
		} 
		else 
		{
			// 启动特效处理页面
			Intent intent = new Intent();
			intent.setClass(this, EffectsProcessingActivity.class);
			intent.putExtras(extras);
			startActivity(intent);
			finish();
		}

	}

	private void saveOutput(Bitmap croppedImage) {
		if (mSaveUri != null) {
			OutputStream outputStream = null;
			try {
				outputStream = mContentResolver.openOutputStream(mSaveUri);
				if (outputStream != null) {
					croppedImage.compress(mOutputFormat, 75, outputStream);
				}
			} catch (IOException ex) {
				// TODO: report error to caller
				LogUtil.e("Cannot open file: " + mSaveUri, ex);
			} finally {
				Util.closeSilently(outputStream);
			}
			Bundle extras = new Bundle();
			setResult(RESULT_OK, new Intent(mSaveUri.toString())
					.putExtras(extras));
		} else {
			Bundle extras = new Bundle();
			extras.putString("rect", mCrop.getCropRect().toString());

			File oldPath = new File(mOriginImagePath);
			File directory = new File(oldPath.getParent());

			int x = 0;
			String fileName = oldPath.getName();
			fileName = fileName.substring(0, fileName.lastIndexOf("."));

			// Try file-1.jpg, file-2.jpg, ... until we find a filename which
			// does not exist yet.
			while (true) {
				x += 1;
				String candidate = directory.toString() + "/" + fileName + "-"
						+ x + ".jpg";
				boolean exists = (new File(candidate)).exists();
				if (!exists) {
					break;
				}
			}

			try {
				// Uri newUri = ImageManager.addImage(
				// mContentResolver,
				// mImage.getTitle(),
				// mImage.getDateTaken(),
				// null, // TODO this null is going to cause us to lose
				// // the location (gps).
				// 0, // TODO this is going to cause the orientation
				// // to reset.
				// directory.toString(),
				// fileName + "-" + x + ".jpg");
				//
				// Cancelable<Void> cancelable =
				// ImageManager.storeImage(
				// newUri,
				// mContentResolver,
				// 0, // TODO fix this orientation
				// croppedImage,
				// null);
				//
				// cancelable.get();
				// setResult(RESULT_OK, new Intent()
				// .setAction(newUri.toString())
				// .putExtras(extras));
			} catch (Exception ex) {
				// basically ignore this or put up
				// some ui saying we failed
				LogUtil.e("store image fail, continue anyway", ex);
			}
		}
		croppedImage.recycle();
		finish();
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {

		super.onConfigurationChanged(newConfig);
		if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
			//mLL_land.setVisibility(View.INVISIBLE);
			mLL_port.setVisibility(View.VISIBLE);
		} else {
			//mLL_land.setVisibility(View.VISIBLE);
			mLL_port.setVisibility(View.INVISIBLE);
		}
	}

	@Override
	protected void onPause() {
		super.onPause();
		BitmapManager.instance().cancelThreadDecoding(mDecodingThreads);
	}

	@Override
	protected void onDestroy() {
		// if (mAllImages != null) {
		// mAllImages.deactivate();
		// }
		if(null != mBitmap && !mBitmap.isRecycled())
			mBitmap.recycle();
		super.onDestroy();
	}

	Runnable mRunFaceDetection = new Runnable() {
		float mScale = 1F;
		Matrix mImageMatrix;
		FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
		int mNumFaces;

		// For each face, we create a HightlightView for it.
		@SuppressWarnings("unused")
		private void handleFace(FaceDetector.Face f) {
			PointF midPoint = new PointF();

			int r = ((int) (f.eyesDistance() * mScale)) * 2;
			f.getMidPoint(midPoint);
			midPoint.x *= mScale;
			midPoint.y *= mScale;

			int midX = (int) midPoint.x;
			int midY = (int) midPoint.y;

			HighlightView hv = new HighlightView(mImageView);

			int width = mBitmap.getWidth();
			int height = mBitmap.getHeight();

			Rect imageRect = new Rect(0, 0, width, height);

			RectF faceRect = new RectF(midX, midY, midX, midY);
			faceRect.inset(-r, -r);
			if (faceRect.left < 0) {
				faceRect.inset(-faceRect.left, -faceRect.left);
			}

			if (faceRect.top < 0) {
				faceRect.inset(-faceRect.top, -faceRect.top);
			}

			if (faceRect.right > imageRect.right) {
				faceRect.inset(faceRect.right - imageRect.right, faceRect.right
						- imageRect.right);
			}

			if (faceRect.bottom > imageRect.bottom) {
				faceRect.inset(faceRect.bottom - imageRect.bottom,
						faceRect.bottom - imageRect.bottom);
			}

			hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop,
					mAspectX != 0 && mAspectY != 0);

			mImageView.add(hv);
		}

		// Create a default HightlightView if we found no face in the picture.
		private void makeDefault() 
		{
			HighlightView hv = new HighlightView(mImageView);

			int width = mBitmap.getWidth();
			int height = mBitmap.getHeight();

			Rect imageRect = new Rect(0, 0, width, height);

			int cropWidth = 0;
			int cropHeight = 0;
			if(mSqure == true)
			{
				// make the default size about 4/5 of the width or height
				cropWidth = Math.min(width, height); //* 5 / 5;width; 
				cropHeight = cropWidth;// ;height
			}
			else
			{
				cropWidth = width; 
				cropHeight = height;
			}
			

			if (mAspectX != 0 && mAspectY != 0) {
				if (mAspectX > mAspectY) {
					cropHeight = cropWidth * mAspectY / mAspectX;
				} else if(mAspectX == mAspectY)
				{
					int min = Math.min(cropWidth, cropHeight);
					cropWidth = cropHeight = min;
				}
				else
				{
					cropWidth = cropHeight * mAspectX / mAspectY;
				}
				
			}

			int x = (width - cropWidth) / 2;
			int y = (height - cropHeight) / 2;

			RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
			hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
					mAspectX != 0 && mAspectY != 0);
			mImageView.add(hv);
		}

		// Scale the image down for faster face detection.
		private Bitmap prepareBitmap() {
			if (mBitmap == null) {
				return null;
			}

			// 256 pixels wide is enough.
			if (mBitmap.getWidth() > 256) {
				mScale = 256.0F / mBitmap.getWidth();
			}
			Matrix matrix = new Matrix();
			matrix.setScale(mScale, mScale);
			Bitmap faceBitmap = BitmapUtil.instance().createBitmap(mBitmap, 0,
					0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);
			return faceBitmap;
		}

		public void run() {
			mImageMatrix = mImageView.getImageMatrix();
			Bitmap faceBitmap = prepareBitmap();

			mScale = 1.0F / mScale;
			if (faceBitmap != null && mDoFaceDetection) {
				FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
						faceBitmap.getHeight(), mFaces.length);
				mNumFaces = detector.findFaces(faceBitmap, mFaces);
			}

			if (faceBitmap != null && faceBitmap != mBitmap) {
				faceBitmap.recycle();
			}

			mHandler.post(new Runnable() {
				public void run() {
					mWaitingToPick = mNumFaces > 1;
					// if (mNumFaces > 0) {
					// for (int i = 0; i < mNumFaces; i++) {
					// handleFace(mFaces[i]);
					// }
					// } else {
					makeDefault();
					// }
					mImageView.invalidate();
					if (mImageView.mHighlightViews.size() == 1) {
						mCrop = mImageView.mHighlightViews.get(0);
						mCrop.setFocus(true);
					}

					if (mNumFaces > 1) {
						Toast t = Toast.makeText(CropImage.this,
								R.string.multiface_crop_help,
								Toast.LENGTH_SHORT);
						t.show();
					}
				}
			});
		}
	};

	@Override
	public void onClick(View v) {
		int id = v.getId();
		switch (id) {
		case R.id.btn_back:
		case R.id.discard:
		//case R.id.discard_land:
			// mBitmap.recycle();
			setResult(RESULT_CANCELED);
			finish();
			break;
		case R.id.btn_rotate_left:
		//case R.id.btn_rotate_left_land:
			onRotateClicked(-90);
			break;
		case R.id.btn_rotate_right:
		//case R.id.btn_rotate_right_land:
			onRotateClicked(90);
			break;
		case R.id.btn_ok:
		case R.id.save:
		//case R.id.save_land:
			onSaveClicked();
			break;
		default:
			break;
		}
	}
}

class CropImageView extends ImageViewTouchBase {
	ArrayList<HighlightView> mHighlightViews = new ArrayList<HighlightView>();
	HighlightView mMotionHighlightView = null;
	float mLastX, mLastY;
	int mMotionEdge;

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		if (mBitmapDisplayed.getBitmap() != null) {
			for (HighlightView hv : mHighlightViews) {
				hv.mMatrix.set(getImageMatrix());
				hv.invalidate();
				if (hv.mIsFocused) {
					centerBasedOnHighlightView(hv);
				}
			}
		}
	}

	public CropImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected void zoomTo(float scale, float centerX, float centerY) {
		super.zoomTo(scale, centerX, centerY);
		for (HighlightView hv : mHighlightViews) {
			hv.mMatrix.set(getImageMatrix());
			hv.invalidate();
		}
	}

	@Override
	protected void zoomIn() {
		super.zoomIn();
		for (HighlightView hv : mHighlightViews) {
			hv.mMatrix.set(getImageMatrix());
			hv.invalidate();
		}
	}

	@Override
	protected void zoomOut() {
		super.zoomOut();
		for (HighlightView hv : mHighlightViews) {
			hv.mMatrix.set(getImageMatrix());
			hv.invalidate();
		}
	}

	@Override
	protected void postTranslate(float deltaX, float deltaY) {
		super.postTranslate(deltaX, deltaY);
		for (int i = 0; i < mHighlightViews.size(); i++) {
			HighlightView hv = mHighlightViews.get(i);
			hv.mMatrix.postTranslate(deltaX, deltaY);
			hv.invalidate();
		}
	}

	// According to the event's position, change the focus to the first
	// hitting cropping rectangle.
	private void recomputeFocus(MotionEvent event) {
		for (int i = 0; i < mHighlightViews.size(); i++) {
			HighlightView hv = mHighlightViews.get(i);
			hv.setFocus(false);
			hv.invalidate();
		}

		for (int i = 0; i < mHighlightViews.size(); i++) {
			HighlightView hv = mHighlightViews.get(i);
			int edge = hv.getHit(event.getX(), event.getY());
			if (edge != HighlightView.GROW_NONE) {
				if (!hv.hasFocus()) {
					hv.setFocus(true);
					hv.invalidate();
				}
				break;
			}
		}
		invalidate();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		CropImage cropImage = (CropImage) getContext();
		if (cropImage.mSaving) {
			return false;
		}

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			if (cropImage.mWaitingToPick) {
				recomputeFocus(event);
			} else {
				for (int i = 0; i < mHighlightViews.size(); i++) {
					HighlightView hv = mHighlightViews.get(i);
					int edge = hv.getHit(event.getX(), event.getY());
					if (edge != HighlightView.GROW_NONE) {
						mMotionEdge = edge;
						mMotionHighlightView = hv;
						mLastX = event.getX();
						mLastY = event.getY();
						mMotionHighlightView
								.setMode((edge == HighlightView.MOVE) ? HighlightView.ModifyMode.Move
										: HighlightView.ModifyMode.Grow);
						break;
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (cropImage.mWaitingToPick) {
				for (int i = 0; i < mHighlightViews.size(); i++) {
					HighlightView hv = mHighlightViews.get(i);
					if (hv.hasFocus()) {
						cropImage.mCrop = hv;
						for (int j = 0; j < mHighlightViews.size(); j++) {
							if (j == i) {
								continue;
							}
							mHighlightViews.get(j).setHidden(true);
						}
						centerBasedOnHighlightView(hv);
						((CropImage) getContext()).mWaitingToPick = false;
						return true;
					}
				}
			} else if (mMotionHighlightView != null) {
				centerBasedOnHighlightView(mMotionHighlightView);
				mMotionHighlightView.setMode(HighlightView.ModifyMode.None);
			}
			mMotionHighlightView = null;
			break;
		case MotionEvent.ACTION_MOVE:
			if (cropImage.mWaitingToPick) {
				recomputeFocus(event);
			} else if (mMotionHighlightView != null) {
				mMotionHighlightView.handleMotion(mMotionEdge, event.getX()
						- mLastX, event.getY() - mLastY);
				mLastX = event.getX();
				mLastY = event.getY();

				if (true) {
					// This section of code is optional. It has some user
					// benefit in that moving the crop rectangle against
					// the edge of the screen causes scrolling but it means
					// that the crop rectangle is no longer fixed under
					// the user's finger.
					ensureVisible(mMotionHighlightView);
				}
			}
			break;
		}

		switch (event.getAction()) {
		case MotionEvent.ACTION_UP:
			center(true, true);
			break;
		case MotionEvent.ACTION_MOVE:
			// if we're not zoomed then there's no point in even allowing
			// the user to move the image around. This call to center puts
			// it back to the normalized location (with false meaning don't
			// animate).
			if (getScale() == 1F) {
				center(true, true);
			}
			break;
		}

		return true;
	}

	// Pan the displayed image to make sure the cropping rectangle is visible.
	private void ensureVisible(HighlightView hv) {
		Rect r = hv.mDrawRect;

		int panDeltaX1 = Math.max(0, getLeft() - r.left);
		int panDeltaX2 = Math.min(0, getRight() - r.right);

		int panDeltaY1 = Math.max(0, getTop() - r.top);
		int panDeltaY2 = Math.min(0, getBottom() - r.bottom);

		int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
		int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;

		if (panDeltaX != 0 || panDeltaY != 0) {
			panBy(panDeltaX, panDeltaY);
		}
	}

	// If the cropping rectangle's size changed significantly, change the
	// view's center and scale according to the cropping rectangle.
	private void centerBasedOnHighlightView(HighlightView hv) {
		Rect drawRect = hv.mDrawRect;

		float width = drawRect.width();
		float height = drawRect.height();

		float thisWidth = getWidth();
		float thisHeight = getHeight();

		float z1 = thisWidth / width * .6F;
		float z2 = thisHeight / height * .6F;

		float zoom = Math.min(z1, z2);
		zoom = zoom * this.getScale();
		zoom = Math.max(1F, zoom);

		if ((Math.abs(zoom - getScale()) / zoom) > .1) {
			float[] coordinates = new float[] { hv.mCropRect.centerX(),
					hv.mCropRect.centerY() };
			getImageMatrix().mapPoints(coordinates);
			zoomTo(zoom, coordinates[0], coordinates[1], 300F);
		}

		ensureVisible(hv);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		for (int i = 0; i < mHighlightViews.size(); i++) {
			mHighlightViews.get(i).draw(canvas);
		}
	}

	public void add(HighlightView hv) {
		mHighlightViews.add(hv);
		invalidate();
	}
}
